#!/bin/bash
#
# ulpatch config - to get cflags and ldflags needed by ulpatch.elf's
# compilation.
#
# Copyright (C) 2024-2025 Rong Tao
#
# The latest version of this software can be obtained here:
#
# https://github.com/rtoax/ulpatch
#
set -e

readonly ANSI_BOLD="\033[1m"
readonly ANSI_GRAY="\033[2m"
readonly ANSI_UNDERLINE="\033[4m"
readonly ANSI_RESET="\033[m"
readonly ULPATCH_VERSION_MAJOR=@ULPATCH_VERSION_MAJOR@
readonly ULPATCH_VERSION_MINOR=@ULPATCH_VERSION_MINOR@
readonly ULPATCH_VERSION_PATCH=@ULPATCH_VERSION_PATCH@
readonly ULPATCH_VERSION="@ULP_CONFIG_VERSION@"

CC=gcc
LD=ld

verbose=
show_version=
already_run=
run_cflags=
run_libs=
run_ldflags=
declare -a cflags libs ldflags


__usage__()
{
	echo -e "
${ANSI_BOLD}NAME${ANSI_RESET}
    ulpconfig - Print ULPatch compilation options

${ANSI_BOLD}SYNOPSIS${ANSI_RESET}
    ${ANSI_BOLD}ulpconfig${ANSI_RESET} [${ANSI_UNDERLINE}options${ANSI_RESET}]

${ANSI_BOLD}DESCRIPTION${ANSI_RESET}
    ulpconfig makes it easier to build ulpatch that use ULPatch. It  can print
    the  compiler flags, linker  flags  and  object  libraries  needed  to link
    against ULPatch.

    Good output to ${ANSI_BOLD}stdout${ANSI_RESET}, Wrong output to ${ANSI_BOLD}stderr${ANSI_RESET}.

    This program is a basic command of ULPatch.

${ANSI_BOLD}ARGUMENT${ANSI_RESET}

   --cflags           Print the C compiler flags needed to use ULPatch headers.

   --ldflags          Print the flags needed to link against ULPatch libraries.

   --libs             Print all the libraries needed to link against the speci-
                      fied ULPatch components, including any dependencies.

   --cc [COMPILER]    Specify compiler, default: ${CC}, you should specify this
                      argument before --cflags.

   --ld [LINKER]      Specify linker, default: ${LD}, you should specify this
                      argument before --ldflags and --libs.

   -h, --help         show this help information
   -V, --version      show version

   -v, --verbose      show verbose of running

${ANSI_BOLD}SEE ALSO${ANSI_RESET}
   ulpinfo(8), ulftrace(8), ultask(8), ulpconfig(8)

" >&2

	exit ${1-0}
}

goodbye()
{
	local ret=$?
}

trap "goodbye" EXIT

# For example
# check_cc_option -mno-sse3 : return YES if support -mno-sse3
# check_cc_option -mno-not-exist : return NO
check_cc_option()
{
	local flag=$1
	echo "int main(void) { return 0; }" | \
		${CC} -x c -Wall - ${flag} -o /dev/null 2>/dev/null 1>/dev/null || {
			echo NO && true && return 0
		}
	echo YES && return 0
}

check_cc_option_and_add_cflags()
{
	local flag=$1
	if [[ $(check_cc_option ${flag}) == YES ]]; then
		cflags+=( ${flag} )
	fi
}

set_ld()
{
	LD=$1
	${LD} --help 2>&1 >/dev/null || {
		echo "ERROR: Bad ${LD}" >&2
		exit 1
	}
}

set_cc()
{
	CC=$1
	${CC} --help 2>&1 >/dev/null || {
		echo "ERROR: Bad ${CC}" >&2
		exit 1
	}
}

print_cflags()
{
	cflags+=( "-I/usr/include/" )
	cflags+=( "-D_GNU_SOURCE" )
	# See ulpatch/meta.h
	cflags+=( "-D__ULP_DEV=1" )

	if [[ $(uname -m) == x86_64 ]]; then
		check_cc_option_and_add_cflags -m64
		check_cc_option_and_add_cflags -mno-sse
		check_cc_option_and_add_cflags -mno-mmx
		check_cc_option_and_add_cflags -mno-sse2
		check_cc_option_and_add_cflags -mno-3dnow
		check_cc_option_and_add_cflags -mno-avx
		check_cc_option_and_add_cflags -fcf-protection=branch
		check_cc_option_and_add_cflags -fno-jump-tables
		check_cc_option_and_add_cflags -falign-jumps=1
	elif [[ $(uname -m) == aarch64 ]]; then
		check_cc_option_and_add_cflags -fshort-wchar
		check_cc_option_and_add_cflags -funsigned-char
		check_cc_option_and_add_cflags -fno-strict-aliasing
		check_cc_option_and_add_cflags -mgeneral-regs-only
		check_cc_option_and_add_cflags -fno-asynchronous-unwind-tables
		check_cc_option_and_add_cflags -fno-unwind-tables
		check_cc_option_and_add_cflags -mbranch-protection=pac-ret
		check_cc_option_and_add_cflags -fno-delete-null-pointer-checks
	# FIXME: Move arches
	fi

	echo -n ${cflags[@]}
}

print_ldflags()
{
	ldflags+=( "-relocatable" )
	ldflags+=( "--build-id=sha1" )
	ldflags+=( "-z noexecstack" )

	if [[ "$(${LD} --help | grep no-warn-rwx-segments 2>/dev/null || true)" ]]; then
		ldflags+=( "--no-warn-rwx-segments" )
	fi

	echo -n ${ldflags[@]}
}

print_libs()
{
	echo -n ${libs[@]}
}

TEMP=$(getopt \
	--options vhV \
	--long cc: \
	--long ld: \
	--long cflags \
	--long ldflags \
	--long libs \
	--long help \
	--long version \
	--long verbose \
	-n ulpconfig -- "$@")

test $? != 0 && __usage__ 1

eval set -- "$TEMP"

while true; do
	case $1 in
	--cflags)
		shift
		already_run=YES
		run_cflags=YES
		;;
	--ldflags)
		shift
		already_run=YES
		run_ldflags=YES
		;;
	--libs)
		shift
		already_run=YES
		run_libs=YES
		;;
	--cc)
		shift
		set_cc $1
		shift
		;;
	--ld)
		shift
		set_ld $1
		shift
		;;
	-h|--help)
		shift
		__usage__
		;;
	-v|--verbose)
		shift
		verbose=YES
		;;
	-V|--version)
		shift
		show_version=YES
		;;
	--)
		shift
		break
		;;
	esac
done

if [[ ${show_version} ]]; then
	echo "ulpconfig ${ULPATCH_VERSION}"
	if [[ ${verbose} ]]; then
		echo ULPATCH_VERSION_MAJOR = ${ULPATCH_VERSION_MAJOR}
		echo ULPATCH_VERSION_MINOR = ${ULPATCH_VERSION_MINOR}
		echo ULPATCH_VERSION_PATCH = ${ULPATCH_VERSION_PATCH}
	fi
	exit 0
fi

if [[ ${verbose} ]]; then
	PS4='+${BASH_SOURCE}:${LINENO}:${FUNCNAME[0]}: '
	set -x
fi

# Check CC and LD
set_cc ${CC}
set_ld ${LD}

if [[ -z ${already_run} ]]; then
	__usage__ 1
else
	[[ ${run_cflags} ]] && print_cflags && echo -n " "
	[[ ${run_ldflags} ]] && print_ldflags && echo -n " "
	[[ ${run_libs} ]] && print_libs
	# Need newline(\n) here
	echo
fi

